/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "utils.h"
#include <locale.h>
#include <poll.h>
#include <setjmp.h>
#include <signal.h>
#include <strings.h>
#include <stropts.h>
#include <syslog.h>
#include <sys/sysmsg_impl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/systeminfo.h>
#include <sys/termios.h>
#include <sys/types.h>

#define	CONSADM			"/usr/sbin/consadm"
#define	CONSADMD		"/usr/sbin/consadmd"
#define	CONSADMLOCK		"/tmp/CoNsAdM.lck"
#define	CONSDAEMON		"consadmd"
#define	MSGLOG			"/dev/msglog"
#define	CONSOLE			"/dev/console"
#define	WSCONS			"/dev/wscons"
#define	CONSCONFIG		"/etc/consadm.conf"
#define	SETCONSOLEPID		"/etc/consadm.pid"

#define	CONFIG			0
#define	UNCONFIG		1
#define	COMMENT			'#'
#define	NEWLINE			'\n'
#define	SPACE			' '
#define	TAB			'	'

#define	E_SUCCESS	0		/* Exit status for success */
#define	E_ERROR		1		/* Exit status for error */
#define	E_USAGE		2		/* Exit status for usage error */
#define	E_NO_CARRIER	3		/* Exit status for no carrier */

/* useful data structures for lock function */
static struct flock fl;
#define	LOCK_EX F_WRLCK

static char usage[] =
	"Usage:	\n"
	"\tconsadm [ -p ] [ -a device ... ]\n"
	"\tconsadm [ -p ] [ -d device ... ]\n"
	"\tconsadm [ -p ]\n";

/* data structures ... */
static char conshdr[] =
	"#\n# consadm.conf\n#"
	"# Configuration parameters for console message redirection.\n"
	"# Do NOT edit this file by hand -- use consadm(8) instead.\n"
	"#\n";
const char *pname;		/* program name */
static sigjmp_buf deadline;

/* command line arguments */
static int display;
static int persist;
static int addflag;
static int deleteflag;

/* function headers */
static void setaux(char *);
static void unsetaux(char *);
static void getconsole(void);
static boolean_t has_carrier(int fd);
static boolean_t modem_support(int fd);
static void setfallback(char *argv[]);
static void removefallback(void);
static void fallbackdaemon(void);
static void persistlist(void);
static int verifyarg(char *, int);
static int safeopen(char *);
static void catch_term(void);
static void catch_alarm(void);
static void catch_hup(void);
static void cleanup_on_exit(void);
static void addtolist(char *);
static void removefromlist(char *);
static int pathcmp(char *, char *);
static int lckfunc(int, int);
typedef void (*sig_handler_t)();
static int getlock(void);

/*
 * In main, return codes carry the following meaning:
 * 0 - successful
 * 1 - error during the command execution
 */

int
main(int argc, char *argv[])
{
	int	index;
	struct	sigaction sa;
	int	c;
	char	*p = strrchr(argv[0], '/');

	if (p == NULL)
		p = argv[0];
	else
		p++;

	pname = p;

	(void) setlocale(LC_ALL, "");
#if	!defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
#endif
	(void) textdomain(TEXT_DOMAIN);

	if (getuid() != 0)
		die(gettext("must be root to run this program\n"));

	/*
	 * Handle normal termination signals that may be received.
	 */
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = 0;
	(void) sigemptyset(&sa.sa_mask);
	(void) sigaction(SIGHUP, &sa, NULL);
	(void) sigaction(SIGINT, &sa, NULL);
	(void) sigaction(SIGQUIT, &sa, NULL);
	(void) sigaction(SIGTERM, &sa, NULL);

	/*
	 * To make sure persistent state gets removed.
	 */
	sa.sa_handler = cleanup_on_exit;
	sa.sa_flags = 0;
	(void) sigemptyset(&sa.sa_mask);
	(void) sigaction(SIGSEGV, &sa, NULL);
	(void) sigaction(SIGILL, &sa, NULL);
	(void) sigaction(SIGABRT, &sa, NULL);
	(void) sigaction(SIGBUS, &sa, NULL);

	if (strcmp(pname, CONSDAEMON) == 0) {
		fallbackdaemon();
		return (E_SUCCESS);
	}

	if (argc == 1)
		display++;
	else {
		while ((c = getopt(argc, argv, "adp")) != EOF)  {
			switch (c) {
			case 'a':
				addflag++;
				break;
			case 'd':
				deleteflag++;
				break;
			case 'p':
				persist++;
				break;
			default:
				(void) fprintf(stderr, gettext(usage));
				exit(E_USAGE);
				/*NOTREACHED*/
			}
		}
	}

	if (display) {
		getconsole();
		return (E_SUCCESS);
	}
	if (addflag && deleteflag) {
		(void) fprintf(stderr, gettext(usage));
		return (E_ERROR);
	}
	if (addflag) {
		if (optind == argc) {
			(void) fprintf(stderr, gettext(usage));
			return (E_ERROR);
		}
		/* separately check every device path specified */
		for (index = optind; index < argc; index++) {
			if (verifyarg(argv[index], addflag))
				return (E_ERROR);
		}

		for (index = optind; index < argc; index++) {
			setaux(argv[index]);
			if (persist)
				addtolist(argv[index]);
		}

		/*
		 * start/restart daemon based on the auxilary
		 * consoles at this time.
		 */
		setfallback(argv);
		return (E_SUCCESS);
	} else if (deleteflag) {
		if (optind == argc) {
			(void) fprintf(stderr, gettext(usage));
			return (E_ERROR);
		}
		/* separately check every device path specified */
		for (index = optind; index < argc; index++) {
			if (verifyarg(argv[index], 0))
				return (E_ERROR);
		}

		for (index = optind; index < argc; index++) {
			unsetaux(argv[index]);
			if (persist && deleteflag)
				removefromlist(argv[index]);
		}

		/*
		 * kill off daemon and restart with
		 * new list of auxiliary consoles
		 */
		setfallback(argv);
		return (E_SUCCESS);
	} else if (persist) {
		if (optind < argc) {
			(void) fprintf(stderr, gettext(usage));
			return (E_ERROR);
		}

		persistlist();
		return (E_SUCCESS);
	} else {
		(void) fprintf(stderr, gettext(usage));
		return (E_ERROR);
	}
} /* main */

/* for daemon to handle termination from user command */
static void
catch_term()
{
	exit(E_SUCCESS);
}

/* handle lack of carrier on open */
static void
catch_alarm()
{
	siglongjmp(deadline, 1);
}

/* caught a sighup */
static void
catch_hup()
{
	/*
	 * ttymon sends sighup to consadmd because it has the serial
	 * port open.  We catch the signal here, but process it
	 * within fallbackdaemon().  We ignore the signal if the
	 * errno returned was EINTR.
	 */
}

/* Remove persistent state on receiving signal. */
static void
cleanup_on_exit()
{
	(void) unlink(CONSADMLOCK);
	exit(E_ERROR);
}

/*
 * send ioctl to /dev/sysmsg to route msgs of the device specified.
 */
static void
setaux(char *dev)
{
	int	fd;

	if ((fd = safeopen(SYSMSG)) < 0)
		die(gettext("%s is missing or not a valid device\n"), SYSMSG);

	if (ioctl(fd, CIOCSETCONSOLE, dev) != 0) {
		/*
		 * Let setting duplicate device be warning, consadm
		 * must proceed to set persistence if requested.
		 */
		if (errno == EBUSY)
			die(gettext("%s is already the default console\n"),
			    dev);
		else if (errno != EEXIST)
			die(gettext("cannot get table entry"));
	}
	syslog(LOG_WARNING, "%s: Added auxiliary device %s", CONSADM, dev);

	(void) close(fd);
}

/*
 * Send ioctl to device specified and
 * Remove the entry from the list of auxiliary devices.
 */
static void
unsetaux(char *dev)
{
	int	fd;

	if ((fd = safeopen(SYSMSG)) < 0)
		die(gettext("%s is missing or not a valid device\n"), SYSMSG);

	if (ioctl(fd, CIOCRMCONSOLE, dev) != 0) {
		if (errno == EBUSY)
			die(gettext("cannot unset the default console\n"));
	} else
		syslog(LOG_WARNING, "%s: Removed auxiliary device %s",
		    CONSADM, dev);
	(void) close(fd);
}

static int
getlock(void)
{
	int lckfd;

	if ((lckfd = open(CONSADMLOCK, O_CREAT | O_EXCL | O_WRONLY,
	    S_IRUSR | S_IWUSR)) < 0) {
		if (errno == EEXIST)
			die(gettext("currently busy, try again later.\n"));
		else
			die(gettext("cannot open %s"), CONSADMLOCK);
	}
	if (lckfunc(lckfd, LOCK_EX) == -1) {
		(void) close(lckfd);
		(void) unlink(CONSADMLOCK);
		die(gettext("fcntl operation failed"));
	}
	return (lckfd);
}

static void
addtolist(char *dev)
{
	int	lckfd, fd;
	FILE	*fp, *nfp;
	char	newfile[MAXPATHLEN];
	char	buf[MAXPATHLEN];
	int	len;
	boolean_t	found = B_FALSE;

	/* update file of devices configured to get console msgs. */

	lckfd = getlock();

	/* Open new file */
	(void) snprintf(newfile, sizeof (newfile), "%s%d",
	    CONSCONFIG, (int)getpid());
	if (((fd = creat(newfile, 0644)) < 0) ||
	    ((nfp = fdopen(fd, "w")) == NULL)) {
		(void) close(lckfd);
		(void) unlink(CONSADMLOCK);
		die(gettext("could not create new %s file"), CONSCONFIG);
	}

	/* Add header to new file */
	(void) fprintf(nfp, "%s", conshdr);

	/* Check that the file doesn't already exist */
	if ((fp = fopen(CONSCONFIG, "r")) != NULL) {
		while (fgets(buf, MAXPATHLEN, fp) != NULL) {
			if (buf[0] == COMMENT || buf[0] == NEWLINE ||
			    buf[0] == SPACE || buf[0] == TAB)
				continue;
			len = strlen(buf);
			buf[len - 1] = '\0'; /* Clear carriage return */
			if (pathcmp(dev, buf) == 0) {
				/* they match so use name passed in. */
				(void) fprintf(nfp, "%s\n", dev);
				found = B_TRUE;
			} else
				(void) fprintf(nfp, "%s\n", buf);
		}
	}
	/* User specified persistent settings */
	if (found == B_FALSE)
		(void) fprintf(nfp, "%s\n", dev);

	(void) fclose(fp);
	(void) fclose(nfp);
	(void) rename(newfile, CONSCONFIG);
	(void) close(lckfd);
	(void) unlink(CONSADMLOCK);
}

/* The list in CONSCONFIG gives the persistence capability in the proto */
static void
removefromlist(char *dev)
{
	int	lckfd;
	FILE	*fp, *nfp;
	char	newfile[MAXPATHLEN + 1];
	char	len;
	char	value[MAXPATHLEN + 1];
	boolean_t	newcontents = B_FALSE;

	/* update file of devices configured to get console msgs. */

	lckfd = getlock();

	if ((fp = fopen(CONSCONFIG, "r")) == NULL) {
		(void) close(lckfd);
		(void) unlink(CONSADMLOCK);
		return;
	}

	/* Open new file */
	(void) snprintf(newfile, sizeof (newfile), "%s%d",
	    CONSCONFIG, (int)getpid());
	if ((nfp = fopen(newfile, "w")) == NULL) {
		(void) close(lckfd);
		(void) unlink(CONSADMLOCK);
		die(gettext("cannot create new %s file"), CONSCONFIG);
	}

	/* Add header to new file */
	(void) fprintf(nfp, "%s", conshdr);

	/*
	 * Check whether the path duplicates what is already in the
	 * file.
	 */
	while (fgets(value, MAXPATHLEN, fp) != NULL) {
		/* skip comments */
		if (value[0] == COMMENT || value[0] == NEWLINE ||
		    value[0] == SPACE || value[0] == TAB)
			continue;
		len = strlen(value);
		value[len - 1] = '\0'; /* Clear carriage return */
		if (pathcmp(dev, value) == 0) {
			/* they match so don't write it */
			continue;
		}
		(void) fprintf(nfp, "%s\n", value);
		newcontents = B_TRUE;
	}
	(void) fclose(fp);
	(void) fclose(nfp);
	/* Remove the file if there aren't any auxiliary consoles */
	if (newcontents)
		(void) rename(newfile, CONSCONFIG);
	else {
		(void) unlink(CONSCONFIG);
		(void) unlink(newfile);
	}
	(void) close(lckfd);
	(void) unlink(CONSADMLOCK);
}

static int
pathcmp(char *adev, char *bdev)
{
	struct stat	st1;
	struct stat	st2;

	if (strcmp(adev, bdev) == 0)
		return (0);

	if (stat(adev, &st1) != 0 || !S_ISCHR(st1.st_mode))
		die(gettext("invalid device %s\n"), adev);

	if (stat(bdev, &st2) != 0 || !S_ISCHR(st2.st_mode))
		die(gettext("invalid device %s\n"), bdev);

	if (st1.st_rdev == st2.st_rdev)
		return (0);

	return (1);
}

/*
 * Display configured consoles.
 */
static void
getconsole(void)
{
	int	fd;
	int	bufsize = 0;		/* size of device cache */
	char	*infop, *ptr, *p;	/* info structure for ioctl's */

	if ((fd = safeopen(SYSMSG)) < 0)
		die(gettext("%s is missing or not a valid device\n"), SYSMSG);

	if ((bufsize = ioctl(fd, CIOCGETCONSOLE, NULL)) < 0)
		die(gettext("cannot get table entry\n"));
	if (bufsize == 0)
		return;

	if ((infop = calloc(bufsize, sizeof (char))) == NULL)
		die(gettext("cannot allocate buffer"));

	if (ioctl(fd, CIOCGETCONSOLE, infop) < 0)
		die(gettext("cannot get table entry\n"));

	ptr = infop;
	while (ptr != NULL) {
		p = strchr(ptr, ' ');
		if (p == NULL) {
			(void) printf("%s\n", ptr);
			break;
		}
		*p++ = '\0';
		(void) printf("%s\n", ptr);
		ptr = p;
	}
	(void) close(fd);
}

/*
 * It is supposed that if the device supports TIOCMGET then it
 * might be a serial device.
 */
static boolean_t
modem_support(int fd)
{
	int	modem_state;

	if (ioctl(fd, TIOCMGET, &modem_state) == 0)
		return (B_TRUE);
	else
		return (B_FALSE);
}

static boolean_t
has_carrier(int fd)
{
	int	modem_state;

	if (ioctl(fd, TIOCMGET, &modem_state) == 0)
		return ((modem_state & TIOCM_CAR) != 0);
	else {
		return (B_FALSE);
	}
}

static void
setfallback(char *argv[])
{
	pid_t	pid;
	FILE	*fp;
	char	*cmd = CONSADMD;
	int	lckfd, fd;

	lckfd = getlock();

	/*
	 * kill off any existing daemon
	 * remove /etc/consadm.pid
	 */
	removefallback();

	/* kick off a daemon */
	if ((pid = fork()) == (pid_t)0) {
		/* always fallback to /dev/console */
		argv[0] = cmd;
		argv[1] = NULL;
		(void) close(0);
		(void) close(1);
		(void) close(2);
		(void) close(lckfd);
		if ((fd = open(MSGLOG, O_RDWR)) < 0)
			die(gettext("cannot open %s"), MSGLOG);
		(void) dup2(fd, 1);
		(void) dup2(fd, 2);
		(void) execv(cmd, argv);
		exit(E_SUCCESS);
	} else if (pid == -1)
		die(gettext("%s not started"), CONSADMD);

	if ((fp = fopen(SETCONSOLEPID, "w")) == NULL)
		die(gettext("cannot open %s"), SETCONSOLEPID);
	/* write daemon pid to file */
	(void) fprintf(fp, "%d\n", (int)pid);
	(void) fclose(fp);
	(void) close(lckfd);
	(void) unlink(CONSADMLOCK);
}

/*
 * Remove the daemon that would have implemented the automatic
 * fallback in event of carrier loss on the serial console.
 */
static void
removefallback(void)
{
	FILE	*fp;
	int	pid;

	if ((fp = fopen(SETCONSOLEPID, "r+")) == NULL)
		/* file doesn't exist, so no work to do */
		return;

	if (fscanf(fp, "%d\n", &pid) <= 0) {
		(void) fclose(fp);
		(void) unlink(SETCONSOLEPID);
		return;
	}

	/*
	 * Don't shoot ourselves in the foot by killing init,
	 * sched, pageout, or fsflush.
	 */
	if (pid == 0 || pid == 1 || pid == 2 || pid == 3) {
		(void) unlink(SETCONSOLEPID);
		return;
	}
	/*
	 * kill off the existing daemon listed in
	 * /etc/consadm.pid
	 */
	(void) kill((pid_t)pid, SIGTERM);

	(void) fclose(fp);
	(void) unlink(SETCONSOLEPID);
}

/*
 * Assume we always fall back to /dev/console.
 * parameter passed in will always be the auxiliary device.
 * The daemon will not start after the last device has been removed.
 */
static void
fallbackdaemon(void)
{
	int	fd, sysmfd, ret = 0;
	char	**devpaths;
	pollfd_t	*fds;
	nfds_t	nfds = 0;
	int	index;
	int	pollagain;
	struct	sigaction sa;
	int	bufsize = 0;		/* length of device cache paths */
	int	cachesize = 0;		/* size of device cache */
	char	*infop, *ptr, *p;	/* info structure for ioctl's */

	/*
	 * catch SIGTERM cause it might be coming from user via consadm
	 */
	sa.sa_handler = catch_term;
	sa.sa_flags = 0;
	(void) sigemptyset(&sa.sa_mask);
	(void) sigaction(SIGTERM, &sa, NULL);

	/*
	 * catch SIGHUP cause it might be coming from a disconnect
	 */
	sa.sa_handler = catch_hup;
	sa.sa_flags = 0;
	(void) sigemptyset(&sa.sa_mask);
	(void) sigaction(SIGHUP, &sa, NULL);

	if ((sysmfd = safeopen(SYSMSG)) < 0)
		die(gettext("%s is missing or not a valid device\n"), SYSMSG);

	if ((bufsize = ioctl(sysmfd, CIOCGETCONSOLE, NULL)) < 0)
		die(gettext("cannot get table entry\n"));
	if (bufsize == 0)
		return;

	if ((infop = calloc(bufsize, sizeof (char))) == NULL)
		die(gettext("cannot allocate buffer"));

	if (ioctl(sysmfd, CIOCGETCONSOLE, infop) < 0)
		die(gettext("cannot get table entry\n"));

	ptr = infop;
	while (ptr != NULL) {
		p = strchr(ptr, ' ');
		if (p == NULL) {
			cachesize++;
			break;
		}
		p++;
		cachesize++;
		ptr = p;
	}

	if ((fds = calloc(cachesize, sizeof (struct pollfd))) == NULL)
		die(gettext("cannot allocate buffer"));

	if ((devpaths = calloc(cachesize, sizeof (char *))) == NULL)
		die(gettext("cannot allocate buffer"));

	ptr = infop;
	while (ptr != NULL) {
		p = strchr(ptr, ' ');
		if (p == NULL) {
			if ((fd = safeopen(ptr)) < 0) {
				warn(gettext("cannot open %s, continuing"),
				    ptr);
				break;
			}
			if (!has_carrier(fd)) {
				(void) close(fd);
				warn(gettext(
		    "no carrier on %s, device will not be monitored.\n"),
				    ptr);
				break;
			} else {
				fds[nfds].fd = fd;
				fds[nfds].events = 0;

				if ((devpaths[nfds] =
				    malloc(strlen(ptr) + 1)) == NULL)
					die(gettext("cannot allocate buffer"));

				(void) strcpy(devpaths[nfds], ptr);
				nfds++;
				if (nfds >= cachesize)
					break;
			}
			break;
		}
		*p++ = '\0';

		if ((fd = safeopen(ptr)) < 0) {
			warn(gettext("cannot open %s, continuing"), ptr);
			ptr = p;
			continue;
		}
		if (!has_carrier(fd)) {
			(void) close(fd);
			warn(gettext(
		    "no carrier on %s, device will not be monitored.\n"),
			    ptr);
			ptr = p;
			continue;
		} else {
			fds[nfds].fd = fd;
			fds[nfds].events = 0;

			if ((devpaths[nfds] = malloc(strlen(ptr) + 1)) == NULL)
				die(gettext("cannot allocate buffer"));

			(void) strcpy(devpaths[nfds], ptr);
			nfds++;
			if (nfds >= cachesize)
				break;
		}
		ptr = p;
	}
	(void) close(sysmfd);

	/* no point polling if no devices with carrier */
	if (nfds == 0)
		return;

	for (;;) {
		/* daemon sleeps waiting for a hangup on the console */
		ret = poll(fds, nfds, INFTIM);
		if (ret == -1) {
			/* Check if ttymon is trying to get rid of us */
			if (errno == EINTR)
				continue;
			warn(gettext("cannot poll device"));
			return;
		} else if (ret == 0) {
			warn(gettext("timeout (%d milleseconds) occured\n"),
			    INFTIM);
			return;
		} else {
			/* Go through poll list looking for events. */
			for (index = 0; index < nfds; index++) {
				/* expected result */
				if ((fds[index].revents & POLLHUP) ==
				    POLLHUP) {
					/*
					 * unsetaux console.  Take out of list
					 * of current auxiliary consoles.
					 */
					unsetaux((char *)devpaths[index]);
					warn(gettext(
				    "lost carrier, unsetting console %s\n"),
					    devpaths[index]);
					syslog(LOG_WARNING,
			    "%s: lost carrier, unsetting auxiliary device %s",
					    CONSADM, devpaths[index]);
					free(devpaths[index]);
					devpaths[index] = NULL;
					(void) close(fds[index].fd);
					fds[index].fd = -1;
					fds[index].revents = 0;
					continue;
				}
				if ((fds[index].revents & POLLERR) ==
				    POLLERR) {
					warn(gettext("poll error\n"));
					continue;
				} else if (fds[index].revents != 0) {
					warn(gettext(
					    "unexpected poll result 0x%x\n"),
					    fds[index].revents);
					continue;
				}
			}
			/* check whether any left to poll */
			pollagain = B_FALSE;
			for (index = 0; index < nfds; index++)
				if (fds[index].fd != -1)
					pollagain = B_TRUE;
			if (pollagain == B_TRUE)
				continue;
			else
				return;
		}
	}
}

static void
persistlist(void)
{
	FILE	*fp;
	char	value[MAXPATHLEN + 1];
	int	lckfd;

	lckfd = getlock();

	if ((fp = fopen(CONSCONFIG, "r")) != NULL) {
		while (fgets(value, MAXPATHLEN, fp) != NULL) {
			/* skip comments */
			if (value[0] == COMMENT ||
			    value[0] == NEWLINE ||
			    value[0] == SPACE || value[0] == TAB)
				continue;
			(void) fprintf(stdout, "%s", value);
		}
		(void) fclose(fp);
	}
	(void) close(lckfd);
	(void) unlink(CONSADMLOCK);
}

static int
verifyarg(char *dev, int flag)
{
	struct stat	st;
	int	fd;
	int	ret = 0;

	if (dev == NULL) {
		warn(gettext("specify device(s)\n"));
		ret = 1;
		goto err_exit;
	}

	if (dev[0] != '/') {
		warn(gettext("device name must begin with a '/'\n"));
		ret = 1;
		goto err_exit;
	}

	if ((pathcmp(dev, SYSMSG) == 0) ||
	    (pathcmp(dev, WSCONS) == 0) ||
	    (pathcmp(dev, CONSOLE) == 0)) {
		/* they match */
		warn(gettext("invalid device %s\n"), dev);
		ret = 1;
		goto err_exit;
	}

	if (stat(dev, &st) || ! S_ISCHR(st.st_mode)) {
		warn(gettext("invalid device %s\n"), dev);
		ret = 1;
		goto err_exit;
	}

	/* Delete operation doesn't require this checking */
	if ((fd = safeopen(dev)) < 0) {
		if (flag) {
			warn(gettext("invalid device %s\n"), dev);
			ret = 1;
		}
		goto err_exit;
	}
	if (!modem_support(fd)) {
		warn(gettext("invalid device %s\n"), dev);
		(void) close(fd);
		ret = 1;
		goto err_exit;
	}

	/* Only verify carrier if it's an add operation */
	if (flag) {
		if (!has_carrier(fd)) {
			warn(gettext("failure, no carrier on %s\n"), dev);
			ret = 1;
			goto err_exit;
		}
	}
err_exit:
	return (ret);
}

/*
 * Open the pseudo device, but be prepared to catch sigalarm if we block
 * cause there isn't any carrier present.
 */
static int
safeopen(char *devp)
{
	int	fd;
	struct	sigaction sigact;

	sigact.sa_flags = SA_RESETHAND | SA_NODEFER;
	sigact.sa_handler = catch_alarm;
	(void) sigemptyset(&sigact.sa_mask);
	(void) sigaction(SIGALRM, &sigact, NULL);
	if (sigsetjmp(deadline, 1) != 0)
		return (-1);
	(void) alarm(5);
	/* The sysmsg driver sets NONBLOCK and NDELAY, but what the hell */
	if ((fd = open(devp, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY)) < 0)
		return (-1);
	(void) alarm(0);
	sigact.sa_flags = 0;
	sigact.sa_handler = SIG_DFL;
	(void) sigemptyset(&sigact.sa_mask);
	(void) sigaction(SIGALRM, &sigact, NULL);
	return (fd);
}

static int
lckfunc(int fd, int flag)
{
	fl.l_type = flag;
	return (fcntl(fd, F_SETLKW, &fl));
}
